//
//  AutoCheckService.m
//  MobileChecker
//
//  Created by Lymons on 15-1-6.
//  Copyright (c) 2015年 Lymons. All rights reserved.
//

#import "AutoCheckService.h"
#import <Reachability.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import <CoreLocation/CoreLocation.h>
#import <CoreMotion/CoreMotion.h>

#import "UIDevice+IdentifierAddition.h"

@interface AutoCheckService () <CBCentralManagerDelegate, CLLocationManagerDelegate>

@property (nonatomic) CBCentralManager *cbManager;
@property (nonatomic) CLLocationManager *locManager;

@end

@implementation AutoCheckService

- (instancetype)init {
    self = [super init];
    if (self) {
        _locManager = [[CLLocationManager alloc] init];
        _locManager.delegate = self;
        _locManager.distanceFilter = kCLDistanceFilterNone;
        _locManager.desiredAccuracy = kCLLocationAccuracyBest;
        
        [self requestGpsAuthorization];
    }
    return  self;
}

- (void)requestGpsAuthorization
{
    if ([_locManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
        [_locManager requestWhenInUseAuthorization];
    }
    if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorized) {
        NSLog(@"%@", @"CLLocationManager: still not got authorization.");
    }
    [_locManager startUpdatingLocation];
}

- (void)stopGps
{
    [_locManager stopUpdatingLocation];
    _locManager.delegate = nil;
    _locManager = nil;
}

- (BOOL)nextStepIfNeed:(Steps *)leftSteps subscriber:(id<RACSubscriber>)subscriber result:(DiagnosisCheckResult)result
{
    return [self nextStepIfNeed:leftSteps subscriber:subscriber result:result runningLabel:nil];
}

- (BOOL)nextStepIfNeed:(Steps *)leftSteps subscriber:(id<RACSubscriber>)subscriber result:(DiagnosisCheckResult)result runningLabel:(NSString *)label
{
    return [self nextStepIfNeed:leftSteps subscriber:subscriber result:result runningLabel:label delay:0];
}

- (BOOL)nextStepIfNeedDelay:(Steps *)leftSteps subscriber:(id<RACSubscriber>)subscriber result:(DiagnosisCheckResult)result
{
    return [self nextStepIfNeed:leftSteps subscriber:subscriber result:result runningLabel:nil delay:0.3];
}

- (BOOL)nextStepIfNeed:(Steps *)leftSteps subscriber:(id<RACSubscriber>)subscriber result:(DiagnosisCheckResult)result runningLabel:(NSString *)label delay:(CGFloat)seconds
{
    [subscriber sendNext:@(result)];
    [subscriber sendCompleted];
    
    if (result != DiagnosisCheckResultNoCheck) {
        [leftSteps performSelector:@selector(stepWithLabel:) withObject:label afterDelay:seconds];
        return YES;
    }
    return NO;
}

#pragma mark - Reachbility Methods

- (RACSignal *)networkCheckSignal:(Steps *)leftSteps
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        RACDisposable *timerDispose = nil;
        __block BOOL networkEnable = [AutoCheckService IsEnableWIFI] || [AutoCheckService IsEnable3G];
        if (networkEnable) {
            [self checkNetwork:subscriber];
        } else {
            [subscriber sendNext:[AutoCheckService errorWithString:@"機内モードをオフにしてください。またはインターネットに繋がられるネットワークを選択してください。" code:6666]];
            
            RACSignal *timerSignal = [[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] take:30] doNext:^(id x) {
                if (networkEnable == NO) {
                    networkEnable = [AutoCheckService IsEnableWIFI] || [AutoCheckService IsEnable3G];
                    if (networkEnable) {
                        [self checkNetwork:subscriber];
                    }
                }
            }];
            
            [[timerSignal takeLast:1] subscribeNext:^(id x) {
                [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultUnknown];
            }];
        }

        return [RACDisposable disposableWithBlock:^{
            [timerDispose dispose];
            networkEnable ? [leftSteps step] : nil;
        }];
    }];
}

+ (BOOL) IsEnableWIFI {
    return ([[Reachability reachabilityForLocalWiFi] currentReachabilityStatus] != NotReachable);
}

+ (BOOL) IsEnable3G {
    return ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] != NotReachable);
}

- (void)checkNetwork:(id<RACSubscriber>)subscriber
{
    Reachability* reach = [Reachability reachabilityWithHostname:@"www.apple.com"];
    // Set the blocks
    void (^reachResult)(Reachability *, id<RACSubscriber>, BOOL) = ^(Reachability *reach, id<RACSubscriber> subscriber, BOOL result) {
        dispatch_async(dispatch_get_main_queue(), ^{
            // keep in mind this is called on a background thread
            // and if you are updating the UI it needs to happen
            // on the main thread, like this:
            if (result) {
                NSLog(@"REACHABLE!");
                [subscriber sendNext:@(DiagnosisCheckResultOk)];
            } else {
                NSLog(@"REACHABLE!");
                [subscriber sendNext:@(DiagnosisCheckResultNg)];
            }
            NSLog(@"-- Network current status: %@", reach.currentReachabilityString);
            [subscriber sendCompleted];
        });
    };
    
    reach.reachableBlock = ^(Reachability *reach)
    {
        reachResult(reach, subscriber, YES);
    };
    
    reach.unreachableBlock = ^(Reachability *reach)
    {
        reachResult(reach, subscriber, NO);
    };
    
    // Start the notifier, which will cause the reachability object to retain itself!
    [reach startNotifier];
    
//    NSLog(@"-- current status: %@", reach.currentReachabilityString);
//    if (reach.isReachable) {
//        [subscriber sendNext:@(YES)];
//    } else {
//        [subscriber sendNext:@(NO)];
//    }
//    [subscriber sendCompleted];
}

#pragma mark - Central Methods

- (RACSignal *)bluetoohCheckSignal:(Steps *)leftSteps
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        RACDisposable *timerDispose =  [[[RACSignal interval:30 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(NSDate *now) {
            [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultUnknown runningLabel:@"bluetooth"];
        }];
        
        self.cbManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
        RACSignal *signal = [self rac_signalForSelector:@selector(centralManagerDidUpdateState:) fromProtocol:@protocol(CBCentralManagerDelegate)];
        RACDisposable *dispose = [[[signal map:^id(RACTuple *tuple) {
            CBCentralManager *central = tuple.first;
            NSLog(@"-- Bluetooth current status: %ld", (long)central.state);
            return @(central.state);
        }]
        distinctUntilChanged]
        subscribeNext:^(NSNumber *state) {
            DiagnosisCheckResult result = DiagnosisCheckResultNoSupport;
            switch ([state integerValue]) {
                case CBCentralManagerStateResetting:
                    [subscriber sendNext:[AutoCheckService errorWithString:@"Bluetoothをオンにしてください。" code:5555]];
                    break;
                case CBCentralManagerStateUnsupported:
                    if ([[UIDevice currentDevice].machineShortName isEqualToString:@"iPhone 4"]) {
                        result = DiagnosisCheckResultUnknown;
                    }
                    [self nextStepIfNeed:leftSteps subscriber:subscriber result:result runningLabel:@"bluetooth"];
                    break;
                case CBCentralManagerStateUnknown:
                    [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultUnknown runningLabel:@"bluetooth"];
                    break;
                case CBCentralManagerStateUnauthorized:
                    [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultNg runningLabel:@"bluetooth"];
                    break;
                case CBCentralManagerStatePoweredOff:
                    [subscriber sendNext:[AutoCheckService errorWithString:@"Bluetoothをオンにしてください。" code:5555]];
                    break;
                case CBCentralManagerStatePoweredOn:
                     [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultOk runningLabel:@"bluetooth"];
                    break;
                default:
                    break;
            }
        }];
        
        return [RACDisposable disposableWithBlock:^{
            self.cbManager = nil;
            [dispose dispose];
            [timerDispose dispose];
        }];
    }];
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    // Dummy
}

#pragma mark - GPS Methods

+ (NSError *)errorWithString:(NSString *)message code:(NSInteger)code
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    dict[NSLocalizedFailureReasonErrorKey] = message;
    return [NSError errorWithDomain:@"LocationManagerDomain" code:code userInfo:dict];
}

+ (NSString *)messageWithError:(NSError *)error
{
    return [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
}

- (RACSignal *)gpsCheckSignal:(Steps *)leftSteps
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//        CLLocationManager *locManager = nil;
        RACDisposable *timerDispose =  [[[RACSignal interval:30 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(NSDate *now) {
            [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultUnknown runningLabel:@"Gps"];
        }];
        
        RACDisposable *locDispose = nil;
        RACDisposable *dispose = [RACDisposable disposableWithBlock:^{
            [self.locManager stopUpdatingLocation];
            [locDispose dispose];
            [timerDispose dispose];
        }];
        if ([UIDevice currentDevice].hasGPS == NO) {
            [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultNoSupport];
            return dispose;
        }
        
        if ([CLLocationManager locationServicesEnabled]) {
            RACSignal *signalSuccessed = [self rac_signalForSelector:@selector(locationManager:didUpdateLocations:) fromProtocol:@protocol(CLLocationManagerDelegate)];
            locDispose = [[[signalSuccessed map:^id(RACTuple *tuple) {
                CLLocation *newLocation = [tuple.second lastObject];
                return [[NSString alloc] initWithFormat:@"Lat:%g, Long:%g, Accuray:%g", newLocation.coordinate.latitude, newLocation.coordinate.longitude, newLocation.horizontalAccuracy];
            }]
            distinctUntilChanged]
            subscribeNext:^(NSString *locationString) {
                [subscriber sendNext:locationString];
                [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultOk runningLabel:@"Gps"];
            }];
            RACSignal *signalFailed = [self rac_signalForSelector:@selector(locationManager:didFailWithError:) fromProtocol:@protocol(CLLocationManagerDelegate)];
            [signalFailed subscribeNext:^(RACTuple *tuple) {
                [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultNg runningLabel:@"Gps"];
            }];
        } else {
            [subscriber sendNext:[AutoCheckService errorWithString:@"位置情報サービスをオンにしてください。" code:9999]];
        }
        
        return dispose;
    }];
}

#pragma mark - Magnetic Methods

- (RACSignal *)magneticCheckSignal:(Steps *)leftSteps
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        RACDisposable *timerDispose = nil;
        CLLocationManager *locMgr = nil;
        if ([CLLocationManager headingAvailable]) {
            timerDispose =  [[[RACSignal interval:5 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(NSDate *now) {
                [subscriber sendCompleted];
                [leftSteps step];
            }];
            
            locMgr = [[CLLocationManager alloc] init];
            locMgr.delegate = self;
            locMgr.distanceFilter = kCLDistanceFilterNone;
            locMgr.desiredAccuracy = kCLLocationAccuracyBest;
            [locMgr startUpdatingHeading];
            RACSignal *signalFailed = [self rac_signalForSelector:@selector(locationManager:didFailWithError:) fromProtocol:@protocol(CLLocationManagerDelegate)];
            [signalFailed subscribeNext:^(RACTuple *tuple) {
                // 次ステップの実行の前にNgのシングルを受けられる確保するために、stepを遅延実行するように
                [self nextStepIfNeedDelay:leftSteps subscriber:subscriber result:DiagnosisCheckResultNg];
            }];
            RACSignal *signalMagnetic = [self rac_signalForSelector:@selector(locationManager:didUpdateHeading:) fromProtocol:@protocol(CLLocationManagerDelegate)];
            [signalMagnetic subscribeNext:^(RACTuple *tuple) {
                CLHeading *newHeading = tuple.second;
                CGFloat heading = -1.0f * M_PI * newHeading.magneticHeading / 180.0f;
                NSString *dataString = [[NSString alloc] initWithFormat:@"Magne angle:%+.2f, heading:%+.2f", newHeading.magneticHeading, heading];
                [subscriber sendNext:dataString];
                [subscriber sendNext:@(DiagnosisCheckResultOk)];
            }];
        } else {
            // 次ステップの実行の前にNgのシングルを受けられる確保するために、stepを遅延実行するように
            [self nextStepIfNeedDelay:leftSteps subscriber:subscriber result:DiagnosisCheckResultNoSupport];
        }
        
        return [RACDisposable disposableWithBlock:^{
            [timerDispose dispose];
            [locMgr stopUpdatingHeading];
//            [leftSteps step];
        }];
    }];
}

- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager
{
    return YES;
}

#pragma mark - Gyro Methods

- (RACSignal *)gyroCompassCheckSignal:(Steps *)leftSteps
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        RACDisposable *timerDispose = nil;
        CMMotionManager *motionManager = [[CMMotionManager alloc]init];
        if (motionManager.gyroAvailable == NO) {
            // 次ステップの実行の前にNgのシングルを受けられる確保するために、stepを遅延実行するように
            [self nextStepIfNeedDelay:leftSteps subscriber:subscriber result:DiagnosisCheckResultNoSupport];
        } else {
            timerDispose =  [[[RACSignal interval:5 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(NSDate *now) {
                [subscriber sendCompleted];
                [motionManager stopAccelerometerUpdates];
                [leftSteps step];
            }];
            [motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
                                                withHandler:^(CMAccelerometerData *latestAcc, NSError *error) {
                                                    if (error) {
                                                        [motionManager stopAccelerometerUpdates];
                                                        [subscriber sendNext:@(DiagnosisCheckResultNg)];
                                                    }else{
                                                        NSString *dataString = [NSString stringWithFormat:@"Gyro x:%+.2f, y:%+.2f, z:%+.2f", latestAcc.acceleration.x, latestAcc.acceleration.y, latestAcc.acceleration.z];
                                                        [subscriber sendNext:dataString];
                                                        [subscriber sendNext:@(DiagnosisCheckResultOk)];
                                                    }
            }];
        }
        
        return [RACDisposable disposableWithBlock:^{
            [timerDispose dispose];
//            [leftSteps step];
        }];
    }];
}

#pragma mark - Proximity Methods

- (RACSignal *)proximityCheckSignal:(Steps *)leftSteps
{
    __block BOOL proximityValid = NO;
    __block BOOL proximityDark = NO;
    __block BOOL proximityLight = NO;
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        RACDisposable *timerDispose =  [[[RACSignal interval:10 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(NSDate *now) {
            if (proximityValid == NO) {
                if ([UIDevice currentDevice].hasVibration) {
                    [subscriber sendNext:@(DiagnosisCheckResultNg)];
                } else {
                    [subscriber sendNext:@(DiagnosisCheckResultNoSupport)];
                }
            } else {
                if (proximityLight == NO || proximityDark == NO) {
                    [subscriber sendNext:@(DiagnosisCheckResultNg)];
                } else {
                    [subscriber sendNext:@(DiagnosisCheckResultOk)];
                }
            }
            [subscriber sendCompleted];
//            [leftSteps step];
            [leftSteps performSelector:@selector(step) withObject:nil afterDelay:0.3];
        }];
        
        [self sendLuminaSignal:subscriber];
        
        NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
        BOOL originalState = [UIDevice currentDevice] .proximityMonitoringEnabled;
        [[UIDevice currentDevice]  setProximityMonitoringEnabled:YES];
        id <NSObject> ob = [defaultCenter addObserverForName:UIDeviceProximityStateDidChangeNotification
                                    object:nil
                                     queue:[NSOperationQueue mainQueue]
                                usingBlock:^(NSNotification *note) {
                                    if ([UIDevice currentDevice] .proximityState == YES) {
                                        NSLog(@"Screen becoming dark: %f", [self luminaOfScreen]);
                                        proximityDark = YES;
                                    }
                                    else {
                                        NSLog(@"Screen becoming light: %f", [self luminaOfScreen]);
                                        proximityLight = YES;
                                    }
                                    [self sendLuminaSignal:subscriber];
//                                    [subscriber sendNext:@(DiagnosisCheckResultOk)];
                                    proximityValid = YES;
                                    if (proximityLight && proximityDark) {
                                        [self nextStepIfNeed:leftSteps subscriber:subscriber result:DiagnosisCheckResultOk runningLabel:@"proximity"];
                                    }
                                }];

        return [RACDisposable disposableWithBlock:^{
            [timerDispose dispose];
            [[UIDevice currentDevice] setProximityMonitoringEnabled:originalState];
            [defaultCenter removeObserver:ob];
//            [leftSteps step]; // this is a trap.
        }];
    }];
}

- (CGFloat)sendLuminaSignal:(id<RACSubscriber>) subscriber
{
    CGFloat lumina = [self luminaOfScreen];
    [subscriber sendNext:[[NSString alloc] initWithFormat:@"Lumina :%+.2f", lumina]];
    return lumina;
}

- (CGFloat)luminaOfScreen
{
//    NSNumber *bl = (NSNumber*) CFBridgingRelease(CFPreferencesCopyAppValue(CFSTR("SBBacklightLevel" ), CFSTR("com.apple.springboard")));
//    return [bl floatValue];
    return [UIScreen mainScreen].brightness;
}

#pragma mark - Lumia Methods

- (RACSignal *)luminaCheckSignal:(Steps *)leftSteps
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        CGFloat original = [UIScreen mainScreen].brightness;
        __block DiagnosisCheckResult result = DiagnosisCheckResultNg;
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue addOperationWithBlock:^{
//            __block CGFloat lastLevel = 0.0f;
            CGFloat newLevel = 0.0f;
            int sameCount = 0;
            for (int i=0; i<10; i++) {
                newLevel += 0.1f;
                [[UIScreen mainScreen] setBrightness:newLevel];
                sleep(1);
                CGFloat level = [self luminaOfScreen];
                NSString *levelString = [NSString stringWithFormat:@"%.2f", level];
                NSString *newLevelString = [NSString stringWithFormat:@"%.2f", newLevel];
                if ([levelString isEqualToString:newLevelString]) {
                    if (++sameCount > 2) {
                        result = DiagnosisCheckResultOk;
                    }
                }
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self sendLuminaSignal:subscriber];
                    [subscriber sendNext:@(result)];
                });
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                [subscriber sendCompleted];
                [[UIScreen mainScreen] setBrightness:original];
            });
        }];

        return [RACDisposable disposableWithBlock:^{
            [leftSteps step];
        }];
    }];
}


@end
